# Table of Contents
# AOP
AOP(Aspect Oriented Programming)
은 공통 기능을 추출하여 별도의 모듈로 만드는 것이다. 이를 통해 모든 컴포넌트에 산재했는 공통 기능을 비즈니스 코드와 분리하여 관리할 수 있다. AOP
는 주로 로깅, 트랜잭션, 예외처리, 보안 등의 공통 기능을 뽑아내는데 사용한다.
# 의존성 추가
AOP
를 사용하려면 다음 의존성을 추가해야한다.
// build.gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
}
# @Aspect
회원가입 및 인증을 담당하는 AuthService
가 있다고 가정하자.
package com.yologger.samples.example;
@Service
public class AuthService {
public void logout() {
System.out.println("logout()");
}
public void join(String email, String password) {
System.out.println("join()");
}
public void login(String email, String password) {
System.out.println("login()");
}
}
이제 AuthService
의 메소드가 호출될 때 로그가 출력되도록 AOP
를 구현해보자.
공통 기능을 정의할 클래스에는 @Aspect
어노테이션을 추가한다.
package com.yologger.samples.example;
import org.aspectj.lang.annotation.Aspect;
@Aspect
@Component
public class AuthAspect {
// ..
}
# @PointCut
@PointCut
어노테이션으로 공통 기능을 적용할 타겟을 지정할 수 있다.
예를 들어 AuthService.logout()
메소드는 다음과 같이 타겟팅할 수 있다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(void com.yologger.samples.example.AuthService.logout())")
public void targetLogout() {
}
}
타겟 클래스가 Asepect 클래스와 같은 패키지에 있다면 패키지명을 생략할 수 있다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(void AuthService.logout())")
public void targetLogout() {
}
}
AuthService.join()
메소드는 두 개의 String
타입 인자를 가진다. 이 메소드는 다음과 같이 포인트컷을 설정할 수 있다.
@Aspect
@Component
public class AuthAspect {
// ...
@Pointcut("execution(void AuthService.join(String, String))")
public void targetJoin() {
}
}
모든 인자를 다 커버하려면 ..
를 사용하면 된다.
@Aspect
@Component
public class AuthAspect {
// ...
@Pointcut("execution(void AuthService.join(..))")
public void targetJoin() {
}
}
AuthService
클래스의 모든 메소드를 다 커버하려면 와일드카드(*)
를 사용하면 된다.
@Aspect
@Component
public class AuthAspect {
// ...
@Pointcut("execution(void AuthService.*(..))")
public void targetAll() {
}
}
반환값 타입에 관계없이 메소드를 커버하려면 반환값 타입에도 와일드카드를 사용하면 된다.
@Aspect
@Component
public class AuthAspect {
// ...
@Pointcut("execution(* AuthService.*(..))")
public void targetAll() {
}
}
추가적으로 접근제한자도 제한할 수 있다.
@Aspect
@Component
public class AuthAspect {
// ...
@Pointcut("execution(public * AuthService.*(..))")
public void targetAll() {
}
}
# Advice
포인트컷으로 지정한 타겟이 호출될 때 실행할 기능을 어드바이스(Advice)
라고 한다. 어드바이스는 다섯가지 어노테이션으로 지정할 수 있다.
@Before
@After
@AfterReturning
@AfterThrowing
@Around
# @Before
@Before
을 붙인 메소드는 타겟이 실행되기 전 호출된다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(void AuthService.logout())")
public void targetLogout() {
}
@Before("targetJoin()")
public void beforeLogout() {
System.out.println("Log: Before logout.");
}
}
# @After
@After
를 붙인 메소드는 타겟이 실행된 후 호출된다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(void AuthService.logout())")
public void targetLogout() {
}
@After("targetJoin()")
public void AfterLogout() {
System.out.println("Log: After logout.");
}
}
타겟은 다른 말로 조인포인트(JoinPoint)
라고도 한다. 조인포인트의 상세 정보에 접근하려면 JoinPoint
타입의 인자를 어드바이스 메서드에 선언하면 된다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(void AuthService.logout())")
public void targetLogout() {
}
@After("targetJoin()")
public void AfterLogout(JoinPoint joinPoint) {
System.out.println("Method name: " + joinPoint.getName());
System.out.println("Parameters: " + Arrays.toString(joinPoint.getArgs()));
}
}
# @AfterThrowing
@AfterThrowing
를 붙인 메소드는 타겟이 실행되는 과정에서 예외가 발생하면 호출된다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(void AuthService.logout())")
public void targetLogout() {
}
@AfterThrowing("targetLogout()")
public void AfterThrowingLogout() {
System.out.println("Log: AfterThrowing logout.");
}
}
타겟에서 발생한 예외는 다음과 같이 전달받을 수 있다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(void AuthService.logout())")
public void targetLogout() {
}
@AfterThrowing(pointcut = "targetLogout()", throwing = "exception")
public void AfterThrowingLogout(Throwable exception) {
System.out.println("Log: AfterReturning logout.");
}
}
# @AfterReturning
@AfterReturning
를 붙인 메소드는 타겟이 정상적으로 실행되고 값을 반환한 후 호출된다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(void AuthService.logout())")
public void targetLogout() {
}
@AfterReturning("targeLogout()")
public void AfterReturningLogout() {
System.out.println("Log: AfterReturning logout.");
}
}
타겟에서 값을 반환한다면 어드바이스 메소드에서 다음과 같이 전달받을 수 있다.
@Aspect
@Component
public class AuthAspect {
// ...
@AfterReturning(pointcut = "targetRefreshToken()", returning = "result")
public void AfterReturningRefreshToken(object result) {
System.out.println("Result: " + result);
}
}
# @Around
@Around
를 사용하면 더욱 세밀한 제어가 가능하다. 예를 들어 타겟 메소드를 실행 순서, 실행 시점, 실행 여부 등을 제어할 수 있다. @Around
어드바이스 메소드는 ProceedingJoinPoint
타입의 인자를 전달받는데 이 객체로 타겟 메소드의 실행 순서, 실행시점, 실행 여부 등을 제어한다.
@Aspect
@Component
public class AuthAspect {
@Pointcut("execution(boolean AuthService.logout())")
public void targetLogout() {
}
@Around("targetLogout()")
public void aroundLogout(ProceedingJoinPoint proceedingJoinPoint) {
try {
// 타겟 메소드를 실행한 후 결과값을 반환받는다.
Object result = proceedingJoinPoint.proceed();
} catch (Throwable e) {
// 타겟 메소드 실행 도중 예외 발생
throw new RuntimeException(e);
}
}
}